package com.gmail.volodymyrdotsenko.rooaddon.simpleweb;

import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.text.StrTokenizer;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.shell.CliAvailabilityIndicator;
import org.springframework.roo.shell.CliCommand;
import org.springframework.roo.shell.CliOption;
import org.springframework.roo.shell.CommandMarker;
import org.springframework.roo.support.logging.HandlerUtils;

/**
 * Sample of a command class. The command class is registered by the Roo shell
 * following an automatic classpath scan. You can provide simple user
 * presentation-related logic in this class. You can return any objects from
 * each method, or use the logger directly if you'd like to emit messages of
 * different severity (and therefore different colours on non-Windows systems).
 * 
 * @since 1.1
 */
@Component
// Use these Apache Felix annotations to register your commands class in the Roo
// container
@Service
public class SimplewebCommands implements CommandMarker { // All command types
															// must implement
															// the CommandMarker
															// interface

	private static Logger LOGGER = HandlerUtils
			.getLogger(SimplewebCommands.class);

	@Reference
	private MetadataService metadataService;
	@Reference
	private ProjectOperations projectOperations;
	@Reference
	private TypeLocationService typeLocationService;

	/**
	 * Get a reference to the SimplewebOperations from the underlying OSGi
	 * container
	 */
	@Reference
	private SimplewebOperations operations;

	/**
	 * This method is optional. It allows automatic command hiding in situations
	 * when the command should not be visible. For example the 'entity' command
	 * will not be made available before the user has defined his persistence
	 * settings in the Roo shell or directly in the project.
	 * 
	 * You can define multiple methods annotated with
	 * {@link CliAvailabilityIndicator} if your commands have differing
	 * visibility requirements.
	 * 
	 * @return true (default) if the command should be visible at this stage,
	 *         false otherwise
	 */
	@CliAvailabilityIndicator({ "simpleweb install" })
	public boolean isInstallAvailable() {
		return operations.isCommandAvailable();
	}

	private void replaceParams(File d, final String project,
			final String topPackage) throws IOException {
		if (d == null)
			d = new File(project);

		for (File f : d.listFiles()) {
			if (f.isDirectory())
				replaceParams(f, project, topPackage);
			else
				findAndReplase(f, project, topPackage);
		}
	}

	private void findAndReplase(File f, final String project,
			final String topPackage) throws IOException {

		Path path = Paths.get(f.toURI());
		Charset charset = StandardCharsets.UTF_8;

		String content = new String(Files.readAllBytes(path), charset);
		content = content.replaceAll("\\$\\{project\\}", project);
		content = content.replaceAll("\\$\\{topPackage\\}", topPackage);
		Files.write(path, content.getBytes(charset));
	}

	@CliCommand(value = "simpleweb install", help = "Install new web application")
	public String webInstall(
			@CliOption(key = { "project" }, mandatory = true, help = "Project name") final String project,
			@CliOption(key = { "topPackage" }, mandatory = true, help = "Top level package") final String topPackage) {

		StringBuffer output = new StringBuffer();

		Process p;
		try {
			p = Runtime
					.getRuntime()
					.exec("mvn.bat archetype:generate -DinteractiveMode=false "
							+ " -DarchetypeGroupId=com.gmail.volodymyrdotsenko.maven.web "
							+ " -DarchetypeArtifactId=dvv-web "
							+ " -DarchetypeVersion=1.0 " + " -DgroupId="
							+ topPackage + " -DartifactId=" + project);
			p.waitFor();
			BufferedReader reader = new BufferedReader(new InputStreamReader(
					p.getInputStream()));

			String line = "";
			while ((line = reader.readLine()) != null) {
				output.append(line + "\n");
			}

			replaceParams(null, project, topPackage);

		} catch (Exception e) {
			e.printStackTrace();
		}

		return output.toString();
	}

	@CliAvailabilityIndicator({ "simpleweb scaffold" })
	public boolean isScaffoldAvailable() {
		return true;// controllerOperations.isControllerInstallationPossible();
	}

	@CliCommand(value = "simpleweb scaffold", help = "Create a new scaffold Controller (ie where Roo maintains CRUD functionality automatically)")
	public void webMvcScaffold(
			@CliOption(key = { "class", "" }, mandatory = true, help = "The path and name of the controller object to be created") final JavaType controller,
			@CliOption(key = "backingType", mandatory = true, help = "The name of the form backing type which the controller exposes to the web tier") final JavaType backingType,
			@CliOption(key = "path", mandatory = false, help = "The base path under which the controller listens for RESTful requests (defaults to the simple name of the form backing object)") String path,
			@CliOption(key = "disallowedOperations", mandatory = false, help = "A comma separated list of operations (only create, update, delete allowed) that should not be generated in the controller") final String disallowedOperations) {

		final ClassOrInterfaceTypeDetails cid = typeLocationService
				.getTypeDetails(backingType);
		if (cid == null) {
			LOGGER.warning("The specified entity can not be resolved to a type in your project");
			return;
		}

		if (controller.getSimpleTypeName().equalsIgnoreCase(
				backingType.getSimpleTypeName())) {
			LOGGER.warning("Controller class name needs to be different from the class name of the form backing object (suggestion: '"
					+ backingType.getSimpleTypeName() + "Controller')");
			return;
		}

		final Set<String> disallowedOperationSet = new HashSet<String>();
		if (!"".equals(disallowedOperations)) {
			final String[] disallowedOperationsTokens = new StrTokenizer(
					disallowedOperations, ",").getTokenArray();
			for (final String operation : disallowedOperationsTokens) {
				if (!("create".equals(operation) || "update".equals(operation) || "delete"
						.equals(operation))) {
					LOGGER.warning("-disallowedOperations options can only contain 'create', 'update', 'delete': -disallowedOperations update,delete");
					return;
				}
				disallowedOperationSet.add(operation.toLowerCase());
			}
		}

		if (path.equals("/") || path.equals("/*")) {
			LOGGER.warning("Your application already contains a mapping to '/' or '/*' by default. Please provide a different path.");
			return;
		} else if (path.startsWith("/")) {
			path = path.substring(1);
		}

		operations.createAutomaticController(controller, backingType,
				disallowedOperationSet, path);
	}
}